#include "NodeService.h"
#include <iostream>
#include <protocol/TBinaryProtocol.h>
#include <server/TSimpleServer.h>
#include <transport/TServerSocket.h>
#include <transport/TBufferTransports.h>
#include <transport/TSocket.h>

#include "WrappedNode.h"

using namespace ::apache::thrift;
using namespace ::apache::thrift::protocol;
using namespace ::apache::thrift::transport;
using namespace ::apache::thrift::server;

using boost::shared_ptr;
using namespace mp2;
using namespace std;

// rangecheck_inclusive: the circular equivalent of min < x <= max: if min <
// max, check that min < x <= max; otherwise, check that x <= max (between 0
// and max) or x < min (between min and 2^m)
int rangecheck_inclusive(int min, int max, int x) {
    return min < max ? min < x && x <= max : min < x || x <= max;
}

// rangecheck_exclusive: like rangecheck_inclusive, but return false if x==max
int rangecheck_exclusive(int min, int max, int x) {
    return min < max ? min < x && x < max : min < x || x < max;
}

// print_node: debug function to print the fields of a Node struct
void print_node(Node n) {
    cout << "Node: " << n.id << "\nPort: " << n.port <<  "\nIntroducer Port: " << n.introPort << "\nHash: " << n.hash << "\n";
}

// init: create and open a connection to the node this instance wraps
void WrappedNode::init(int port) {
    boost::shared_ptr<TSocket> socket(new TSocket("localhost", port));
    transport = boost::shared_ptr<TTransport>(new TFramedTransport(socket));
    boost::shared_ptr<TProtocol> protocol(new TBinaryProtocol(transport));

    client = boost::shared_ptr<NodeServiceClient>(new NodeServiceClient(protocol));
    transport->open();

    client->getNode(N);
}

// ensureOpen: check that the connection is open and re-initialize if it is not
void WrappedNode::ensureOpen() {
    if (!transport || !transport->isOpen()) {
        init(N.port);
    }
}

// constructor: given a port, wrap the node that serves on it
WrappedNode::WrappedNode(int port) {
    N.port = port;
    init(port);
}

// constructor: given a node struct, wrap it
WrappedNode::WrappedNode(Node& n) {
    N = n;
    init(N.port);
}

// deconstructor: disconnect the transport
WrappedNode::~WrappedNode() {
    if (transport) transport->close();
}

// all of the following functions are convenience functions wrapping the Thrift
// remote calls of the same names

WrappedNode WrappedNode::findSuccessor(int hash) {
    ensureOpen();
    Node n;
    client->findSuccessor(n, hash);
    return WrappedNode(n);
}

WrappedNode WrappedNode::findPredecessor(int hash) {
    ensureOpen();
    Node n;
    client->findPredecessor(n, hash);
    return WrappedNode(n);
}

WrappedNode WrappedNode::closestPrecedingFinger(int hash) {
    ensureOpen();
    Node n;
    client->closestPrecedingFinger(n, hash);
    return WrappedNode(n);
}

WrappedNode WrappedNode::getSuccessor() {
    ensureOpen();
    Node n;
    client->getSuccessor(n);
    return WrappedNode(n);
}

WrappedNode WrappedNode::getPredecessor() {
    ensureOpen();
    Node n;
    client->getPredecessor(n);
    return WrappedNode(n);
}

string WrappedNode::getTable(int nodeID) {
    ensureOpen();
    string s;
    client->getTable(s, nodeID);
    return s;
}

string WrappedNode::addFile(string filename, string data) {
    ensureOpen();
    string s;
    client->addFile(s, filename, data);
    return s;
}

string WrappedNode::putFile(string filename, string data) {
    ensureOpen();
    string s;
    client->putFile(s, filename, data);
    return s;
}

string WrappedNode::getFile(string filename) {
    ensureOpen();
    string s;
    client->getFile(s, filename);
    return s;
}

string WrappedNode::delFile(string filename) {
    ensureOpen();
    string s;
    client->delFile(s, filename);
    return s;
}

void WrappedNode::notify(const Node& n) {
    ensureOpen();
    client->notify(n);
}

// precedes: check that this node is the predecessor of the given hash
int WrappedNode::precedes(int hash) {
    return rangecheck_inclusive(N.hash, getSuccessor().N.hash, hash);
}
